package com.example.environment;

import liquibase.Contexts;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.diff.DiffResult;
import liquibase.diff.compare.CompareControl;
import liquibase.diff.output.DiffOutputControl;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import liquibase.integration.commandline.CommandLineUtils;
import liquibase.resource.FileSystemResourceAccessor;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import javax.xml.parsers.ParserConfigurationException;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
import java.util.stream.Collectors;

/**
 * @author wangbin
 */
@Service
@Slf4j
public class EnvironmentService {

    @Resource
    private EnvironmentRepository environmentRepository;

    private EnvironmentResult getEnvironmentResult(Long envId, Long tableId) throws DatabaseException {
        Environment environment = environmentRepository.findById(envId).orElseThrow(RuntimeException::new);
        Environment.Table table = environment.getTables().stream().filter(o -> o.getId().equals(tableId)).collect(Collectors.toList()).get(0);
        String fromUrl = "jdbc:mysql://" + environment.getFromHost() + ":" + environment.getFromPort() + "/" + table.getName();
        String toUrl = "jdbc:mysql://" + environment.getToHost() + ":" + environment.getToPort() + "/" + table.getName();
        Database from = getDatabase(fromUrl, environment.getFromUsername(), environment.getFromPassword());
        Database to = getDatabase(toUrl, environment.getToUsername(), environment.getToPassword());
        return new EnvironmentResult(from, to);
    }

    String diff(Long envId, Long tableId) throws LiquibaseException, ParserConfigurationException, IOException {
        log.info("diff-envId:{}", envId);
        log.info("diff-tableId:{}", tableId);
        EnvironmentResult environmentResult = getEnvironmentResult(envId, tableId);
        Liquibase liquibase = getLiquibase(environmentResult.getFrom(), environmentResult.getTo());
        DiffResult diff = liquibase.diff(environmentResult.getFrom(), environmentResult.getTo(), new CompareControl());
        if (diff.areEqual()) {
            return "数据库没有改变";
        }
        CharArrayWriter charArrayWriter = new CharArrayWriter();
        liquibase.update(new Contexts(), charArrayWriter);
        String sql = charArrayWriter.toString();
        String[] split = sql.split("\n");
        StringBuilder result = new StringBuilder();
        for (String s : split) {
            if (ObjectUtils.isEmpty(s)) {
                continue;
            }
            if (s.startsWith("--")) {
                continue;
            }
            if (s.contains("DATABASECHANGELOGLOCK") || s.contains("DATABASECHANGELOG")) {
                continue;
            }
            result.append(s).append("\n");
        }
        if (ObjectUtils.isEmpty(result.toString())) {
            return "数据库没有改变";
        }
        log.info("diffToSql-sql:{}", result.toString());

        return result.toString();
    }

    void update(Long envId, Long tableId) throws ParserConfigurationException, LiquibaseException, IOException {
        log.info("update-envId:{}", envId);
        log.info("update-tableId:{}", tableId);
        EnvironmentResult environmentResult = getEnvironmentResult(envId, tableId);
        Liquibase liquibase = getLiquibase(environmentResult.getFrom(), environmentResult.getTo());
        liquibase.update(new Contexts());
    }

    private Liquibase getLiquibase(Database from, Database to) throws IOException, LiquibaseException, ParserConfigurationException {
        String folder = Files.createTempDirectory("liquibase-").toString();
        String changelog = folder + File.separator + "changelog.xml";
        log.info("getLiquibase-changelog:{}", changelog);
        CommandLineUtils.doDiffToChangeLog(changelog, from, to, new DiffOutputControl(), null, null);
        return new Liquibase(changelog, new FileSystemResourceAccessor(), to);
    }

    private Database getDatabase(String url, String username, String password) throws DatabaseException {
        Connection connection;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Properties info = new Properties();
            info.setProperty("user", username);
            info.setProperty("password", password);
            connection = DriverManager.getConnection(url, info);
        } catch (Exception e) {
            throw new RuntimeException("数据库连接异常");
        }
        return DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    private static class EnvironmentResult {
        private Database from;
        private Database to;
    }
}
